with data_library;          use data_library;
with Ada.text_io;           use Ada.text_io;
with Ada.Integer_Text_IO;   use Ada.Integer_Text_IO;
with Ada.Float_Text_IO;     use Ada.Float_Text_IO;
with Ada.Calendar;          use Ada.Calendar;
with interface_cpp;         use interface_cpp;
with interfaces.c;          use interfaces.c;

package body buses is
    task body bus is
        ------------------------------------------       
        --   Declaration des taches et paquets  --
        ------------------------------------------
        
        -- Odometre
        protected odometer is
            procedure calculate_position;
        end odometer; 
        
        --  bus_info 
        package bus_info is
            function  getBusId return Integer;
            procedure setBusId(id : in Integer);
            function  getBusPosition return Position;
            procedure setBusPosition(p : in Position);
            function  getPositionTime return Float;
            procedure setPositionTime(t : in Float);
            function  getNextStop return T_bus_Stop;
            function  getIdLine return integer;
            procedure changerDeSens;
            function  getSensAller return Boolean;
            procedure setItinerary(itinerar : in bus_Line);
            function  getCurrentDistance return Float;
            procedure init(id : in Integer;itinerar : in bus_Line); 
            task bus_info ;
        end bus_info;  
        
        -- Bus_drive
        protected bus_drive is
            function getSpeed return Float;
            entry start;
            entry stop;
            entry speed_up;
            entry slow_down;
            private
                speed     : Float :=  0.0; -- km/h
                speedLimit: Float := 50.0; 
        end bus_drive;
        
        -- controller
        procedure setNewState(state : in integer);
        task bus_controller is
            entry deviation(numBus : in integer);
            entry setCurrentState;
            entry sendBusPosition;
	    entry sendEmergencyMessage;
        end bus_controller;
        
        ------------------------------------       
        --   Corps des taches et paquets  --
        ------------------------------------       
        
        ------------------       
        --   bus_info   --
        ------------------   
        package body bus_info is 
            pos : Position;
            idBus : Integer;
            itinerary :bus_Line;
            bus_stop_time : Float; --Heure (en sec) du passage au dernier arret
            
            function getBusId return Integer is
            begin
                return idBus;
            end getBusId;
            
            procedure setBusId(id : in Integer) is
            begin
                idBus := id;
            end setBusId;
            
            function getBusPosition return position is
            begin
                return pos;
            end getBusPosition;
            
            procedure setBusPosition(p : in position) is
            begin
                pos:=p;
            end setBusPosition;
            
            procedure setPositionTime(t : in Float) is
            begin
                pos.time := t - bus_stop_time;
            end setPositionTime;
            
            function getPositionTime return Float is
            begin
                return pos.time ;
            end getPositionTime;
            
            function getSensAller return Boolean is
            begin
                return pos.sens_aller ;
            end getSensAller;
            
            --return the distnce between the two bus stop
            function getCurrentDistance return Float is
               d: Float;
            begin
                if getSensAller then -- Aller
                    d := itinerary.TabBS(itinerary.Sommet).distance;
                else
                    d := itinerary.TabBS(itinerary.Sommet - 1).distance;
                end if;
                return d ;
            end getCurrentDistance;

            procedure changerDeSens is
            begin
                pos.sens_aller := not pos.sens_aller;
            end changerDeSens;
                                             
            function getNextStop return T_bus_Stop is
                NextBS :T_bus_Stop ;
            begin
                if getSensAller then -- Aller        
                    if itinerary.Sommet + 1 < itinerary.TabBS'length then
                        itinerary.Sommet := itinerary.Sommet + 1;
                        NextBS := itinerary.TabBS(itinerary.Sommet);
                    else -- je suis au terminus");
                        changerDeSens;
                        itinerary.Sommet := itinerary.Sommet + 1;
                        NextBS := itinerary.TabBS(itinerary.Sommet);                        
                    end if;
                else                -- Retour
                    if (itinerary.Sommet - 1 > 1) then
                        itinerary.Sommet := itinerary.Sommet - 1;
                        NextBS := itinerary.TabBS(itinerary.Sommet);
                    else -- je suis au terminus
                        changerDeSens;
                        itinerary.Sommet := itinerary.Sommet - 1;
                        NextBS := itinerary.TabBS(itinerary.Sommet);                        
                    end if;                    
                end if;
                return NextBS;
            end getNextStop;
            
            function getIdLine return integer is
            begin
                return itinerary.idLine;
            end getIdLine;
            
            procedure setItinerary(itinerar : in bus_Line) is
            begin
                itinerary := itinerar;
            end setItinerary;
            
            procedure init(id : in Integer; itinerar : in bus_Line) is
               p :Position;
            begin
               p.distance := 0.0;
               p.id_bus_stop := itinerar.TabBS(1).idbusstop;
               p.sens_aller := true;
               setBusPosition(p);
               setItinerary(itinerar);
               setBusId(id);
               itinerary.Sommet := 1;
               bus_stop_time   := Float(Seconds (Clock)); 
            end init;
            
            task body bus_info is
                NextBS :T_bus_Stop ;   
            begin
                loop
                    delay 1.0;
                    if bus_drive.getSpeed > 0.0 then
                        odometer.calculate_position;
                        if pos.distance >= getCurrentDistance then
                            NextBS          := getNextStop;
                            bus_drive.stop;
                            pos.id_bus_stop := NextBS.idBusStop;
                            pos.distance    := 0.0;
                            pos.time        := 0.0;
                            bus_stop_time   := Float(Seconds (Clock));
			    bus_controller.sendBusPosition; 
                            delay 2.0; 
                            bus_drive.start ;   
                        end if;
                    end if;        
                end loop; 
                Exception
                    when others =>
                put_line("Exception tache bus info!");
            end bus_info;
        end bus_info;
        
        ------------------       
        --   odometer   --
        ------------------       
        protected body odometer is
            -- Ce calcul est valable que 
            -- si la fonction est appele toute les secondes
            procedure calculate_position is 
                currentSpeed:Float := bus_drive.getSpeed;   
                pos_temp :Position ; 
                        
            begin 
                if bus_drive.getSpeed > 0.0 then
                    pos_temp := (bus_info.getBusPosition);
                    pos_temp.distance := pos_temp.distance + (Float(currentSpeed/3600.0));
                    bus_info.setBusPosition(pos_temp);
                end if;
  
            end calculate_position;
        end odometer;
        
        ------------------       
        --   bus_drive  --
        ------------------
      
        protected body bus_drive is
            function getSpeed return Float is
            begin
                    return speed;
            end getSpeed;
            
            entry start when true is
            begin
                speed := 40.0;
            end start;
            
            entry stop when true is
            begin
                speed := 0.0;
            end stop;
            
            entry speed_up when true is
            begin
                 if speed <= speedLimit - 10.0 then
                    speed := speed + 10.0;
                 end if;   
            end speed_up;
            
            entry slow_down when true is
            begin
                if speed > 11.0 then
                    speed := speed - 10.0;
                end if;
            end slow_down;
        end bus_drive;
        
        ------------------------       
        --   bus_controller   --
        ------------------------
        current_state: integer := 0;
        
        procedure setNewState(state : in integer) is
        begin
            current_state := state;
        end setNewState;
           
        task body bus_controller is     
            current_time: Day_Duration;
	        problem_type : integer :=0;
        begin
        loop
            select
               
                accept deviation(numBus : in integer) do
                    --bus_info.setItinerary(itinerar);
                    put_line("Je devie");
                end deviation;  
            or
                accept setCurrentState do
                    case current_state is
                        -- 2 pour le retard
                        when 2 =>
                            bus_drive.speed_up;
                        -- 1 pour en avance
                        when 1 =>
                            bus_drive.slow_down;
                        when others =>
                            put_line("");
                    end case;
                end setCurrentState;  
            or
                accept sendBusPosition do
                    
                    -- Envoie la nouvelle position a l'operating center
                    current_time := Seconds (Clock);
                    bus_info.setPositionTime(Float(current_time));
                    set_bus_position(bus_info.getIdLine, bus_info.getBusId, new Position'(bus_info.getBusPosition), bus_drive.getSpeed);   
                
                end sendBusPosition;  
	    or 
		accept sendEmergencyMessage do
			announce_problem(bus_info.getIdLine,"agression",new Position'(bus_info.getBusPosition));
			
		end sendEmergencyMessage;                  
            end select;
                
        end loop;
            Exception
                when others =>
            put_line("Exception tache bus controller!"); 
        end bus_controller;          
    
    -- Corps de la tache bus   
    begin
       
       loop 
        select
            --Creation du bus (ne sera appele qu'une fois)
            accept create(id: in Integer;itinerar: in bus_Line) do
                bus_info.init(id,itinerar);
                bus_drive.start;
            end create;
        or
            accept deviation (numBus: in integer) do
                bus_controller.deviation(numBus);
            end deviation;
        or
            accept set_bus_time (retard: in integer) do
                setNewState(retard);
                bus_controller.setCurrentState; 
            end set_bus_time;   
        or 
            --Toute les N secondes le bus envoi sa position a l'OP
            delay N; 
            if bus_drive.getSpeed > 0.0 then
                bus_controller.sendBusPosition; 
            end if;
	        --bus_controller.sendEmergencyMessage;
        end select;
       end loop; 
        Exception
            when others =>
        put_line("Exception tache bus!");
    end bus;
begin
    null;
    Exception
        when others =>
    put_line("Exception package buses!");
end buses;
